# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu, jaewon

from collections import deque
import logging
import math

import memcache


class Constant(object):
  def __init__(self, *, value):
    self._value = value

  def get(self):
    return self._value

  def set(self, value, timestamp):
    pass


class ExponentialDecay(object):
  def __init__(self, *, lifetime=300e9):
    self._logger = logging.getLogger(__name__)

    self._timestamp = 0
    self._exponent = math.exp(math.log(0.5) / lifetime)
    self._value = 0.

  def get(self):
    return self._value

  def set(self, value, timestamp):
    if timestamp < self._timestamp:
      self._logger.warn('Insane timestamp: %f < %f', timestamp, self._timestamp)
      return
    if self._timestamp:
      timedelta = timestamp - self._timestamp
      if timedelta < 1.e3:
        return
      weight = (self._exponent**timedelta)
      self._value = self._value * weight + value * (1. - weight)
    else:
      self._value = value
    self._timestamp = timestamp


class MemcachedExponentialDecay(ExponentialDecay):
  def __init__(self, *, key, lifetime=300e9, host='localhost'):
    super().__init__(lifetime=lifetime)
    self._connection = memcache.Client([host])
    stats = self._connection.get_stats()
    assert stats, 'Memcached is not ready: %s' % stats
    self._key = key
    self._pull()
    self._logger.info('EMA(%s): %f, %f', self._key, self._timestamp, self._value)

  def _pull(self):
    data = self._connection.get(self._key)
    if data:
      self._timestamp = data[0]
      self._value = data[1]

  def _push(self):
    self._connection.set(self._key, (self._timestamp, self._value))

  def set(self, *args, **kwargs):
    super().set(*args, **kwargs)
    self._push()


class MovingSum(object):
  def __init__(self, window: int):
    self._window = window
    self._deque = deque()
    self._sum = 0.

  @property
  def sum(self):
    return self._sum

  @property
  def average(self):
    return self._sum / len(self._deque)

  def __len__(self):
    return len(self._deque)

  def update(self, timestamp: int, value):
    while self._deque:
      elem_ts, elem_value = self._deque[0]
      if timestamp - elem_ts < self._window:
        break
      self._deque.popleft()
      self._sum -= elem_value

    self._deque.append((timestamp, value))
    self._sum += value
